﻿using System.IO;
using Orchard.Environment.Extensions;

namespace ATTools.Helpers
{
    [OrchardFeature("ATTools.VoiceCommands")]
    public static class WaveHelper
    {
        public static byte[] MergeWave(Stream wave1, Stream wave2)
        {
            // Read header of wave1
            WAVEHeader header1 = GetHeaderOfWAVE(wave1);
            WAVEHeader header2 = GetHeaderOfWAVE(wave2);

            WAVEHeader outputHeader = header1;
            outputHeader.ChunkSize = header1.ChunkSize + header2.ChunkSize - 8;
            outputHeader.Subchunk2Size = header1.Subchunk2Size + header2.Subchunk2Size;

            using (MemoryStream outputStream = new MemoryStream())
            {
                //Write the new output header
                WriteHeaderOfWAVEToStream(outputHeader, outputStream);

                //Write data from the 2 wave streams
                WriteDataOfWAVEToStream(header1, wave1, outputStream);
                WriteDataOfWAVEToStream(header2, wave2, outputStream);

                //Reset position
                outputStream.Position = 0;

                return StreamToBytes(outputStream);
            }
        }

        public static byte[] StreamToBytes(Stream wave)
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = wave.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }

        private static void WriteDataOfWAVEToStream(WAVEHeader header, Stream inputWave, Stream outwave)
        {
            inputWave.Seek(header.Subchunk1Size + 28, SeekOrigin.Begin);

            byte[] buffer = new byte[16 * 1024];
            int length = 0;
            while ((length = inputWave.Read(buffer, 0, 1024)) > 0)
            {
                outwave.Write(buffer, 0, length);
            }
        }

        private static void WriteHeaderOfWAVEToStream(WAVEHeader header, Stream outwave)
        {
            BinaryWriter bw = new BinaryWriter(outwave);
            bw.Write(header.ChunkID);
            bw.Write(header.ChunkSize);
            bw.Write(header.Format);

            //The "fmt " subchunk describes the sound data's format:
            bw.Write(header.Subchunk1ID);
            bw.Write(header.Subchunk1Size);
            bw.Write(header.AudioFormat);
            bw.Write(header.NumChannels);
            bw.Write(header.SampleRate);
            bw.Write(header.ByteRate);
            bw.Write(header.BlockAlign);
            bw.Write(header.BitsPerSample);

            // Write ExtraParams if any.
            if (header.ExtraParamSize.HasValue)
            {
                bw.Write(header.ExtraParamSize.Value);
                if (header.ExtraParams != null)
                {
                    bw.Write(header.ExtraParams);
                }
            }

            bw.Write(header.Subchunk2ID);
            bw.Write(header.Subchunk2Size);

        }

        private static WAVEHeader GetHeaderOfWAVE(Stream wave)
        {
            WAVEHeader header = new WAVEHeader();

            BinaryReader br = new BinaryReader(wave);
            header.ChunkID = br.ReadInt32();
            header.ChunkSize = br.ReadInt32();
            header.Format = br.ReadInt32();

            //The "fmt " subchunk describes the sound data's format:
            header.Subchunk1ID = br.ReadInt32();

            //Size of subchunk.
            header.Subchunk1Size = br.ReadInt32();
            int size = header.Subchunk1Size;

            header.AudioFormat = br.ReadInt16();
            header.NumChannels = br.ReadInt16();
            header.SampleRate = br.ReadInt32();
            header.ByteRate = br.ReadInt32();
            header.BlockAlign = br.ReadInt16();
            header.BitsPerSample = br.ReadInt16();

            size -= 16; //The number of bytes reader after Subchunk1Size.
            if (size >= 2)
                header.ExtraParamSize = br.ReadInt16(); //Can be 0 if none.

            if (header.ExtraParamSize.HasValue)
            {
                header.ExtraParams = new byte[header.ExtraParamSize.Value];
                for (int i = 0; i < header.ExtraParamSize.Value; i++)
                {
                    header.ExtraParams[i] = br.ReadByte();
                }
            }

            header.Subchunk2ID = br.ReadInt32();
            header.Subchunk2Size = br.ReadInt32();

            return header;
        }

        /// <summary>
        /// Structure of WAVE Header, in order.
        /// </summary>
        private struct WAVEHeader
        {
            public int ChunkID;
            public int ChunkSize;
            public int Format;
            public int Subchunk1ID;
            public int Subchunk1Size;

            public short AudioFormat;
            public short NumChannels;

            public int SampleRate;
            public int ByteRate;

            public short BlockAlign;

            public short BitsPerSample;
            public short? ExtraParamSize;

            public byte[] ExtraParams;

            public int Subchunk2ID;
            public int Subchunk2Size;


        }
    }
}